<template>
  <section
    class="page-container"
  >
    <section class="options-wrap">
      <span
        :class="[
          'the-radio-button',
          radioValue === 'sphere' ? 'active' : ''
        ]"
        @click="radioValueChange('sphere')"
      >
        球体
      </span>
      <span
        :class="[
          'the-radio-button',
          radioValue === 'cube' ? 'active' : ''
        ]"
        @click="radioValueChange('cube')"
      >
        正方体
      </span>

      <el-popover
        placement="bottom-start"
        title=""
        :width="200"
        trigger="hover"
      >
        <p>
          1. ThreeJs可用于游戏互动、建筑家装、大屏可视化等场景。
          <el-link
            href="https://threejs.org/"
            type="primary"
            target="_blank"
          >
            参 ThreeJs 文档
          </el-link>
        </p>
        <p class="mt-12">
          2.
          <el-link
            href="https://github.com/ue007/three.ts"
            type="primary"
            target="_blank"
          >
            ThreeJs基础学习Demo
          </el-link>
        </p>
        <template #reference>
          <el-icon class="hover">
            <info-filled />
          </el-icon>
        </template>
      </el-popover>
    </section>

    <section
      ref="threeDBox"
      class="three-d-box"
    />

    <div
      ref="tooltipBox"
      class="tooltip-box"
      :style="tooltipPosition"
    >
      <div class="container">
        <div class="title">
          {{ tooltopContent.title }}
        </div>
        <div class="explain">
          {{ tooltopContent.text }}
        </div>
      </div>
    </div>
    <p
      ref="titleBox"
      class="title-text"
      :style="titlePosition"
    >
      {{ tooltopContent.title }}
    </p>
  </section>
</template>

<script lang="ts">
import {
  defineComponent, nextTick, onMounted, ref,
} from 'vue';

import { InfoFilled } from '@element-plus/icons-vue';
import gsap from 'gsap';
import * as Three from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

import backImage from '@/assets/images/house/back.png';
import bottomImage from '@/assets/images/house/bottom.png';
import frontImage from '@/assets/images/house/front.png';
import kitchenImage from '@/assets/images/house/kitchen.jpg';
import leftImage from '@/assets/images/house/left.png';
import livingRoomImage from '@/assets/images/house/living-room.jpg';
import rightImage from '@/assets/images/house/right.png';
import tipImage from '@/assets/images/house/tip.png';
import topImage from '@/assets/images/house/top.png';

let scene:(typeof Three.Scene);
let camera:(typeof Three.PerspectiveCamera);
let renderer:(typeof Three.WebGLRenderer);
let controls:any;
let sphere: any;

const dataList:TypeObject[] = [
  {
    tipsList: [
      {
        position: { x: -200, y: -4, z: -147 },
        content: {
          title: '进入厨房',
          text: '',
          image: 1,
          showTip: false,
          showTitle: true,
        },
      },
      {
        position: { x: -100, y: 0, z: -231 },
        content: {
          title: '洛神赋图',
          text: '描绘了三国时期的才子曹植和洛水女神跨物种的爱情故事。',
          showTip: true,
          showTitle: false,
        },
      },
      {
        position: { x: 150, y: -50, z: -198 },
        content: {
          title: '台灯',
          text: '此电器主要放置在写字台或餐桌上，以供照明之用。',
          showTip: true,
          showTitle: false,
        },
      },
      {
        position: { x: 210, y: 11, z: -140 },
        content: {
          title: '百禄图',
          text: '象征至高无上的皇权地位，它不仅代表长寿，还与“禄”字谐音，故国画中间常借画鹿传达贺寿或加官进爵之意。',
          showTip: true,
          showTitle: false,
        },
      },
      {
        position: { x: 208, y: -12, z: 140 },
        content: {
          title: '智能电视机',
          text: '小米家用智能电视机，智能便捷。',
          showTip: true,
          showTitle: false,
        },
      }
    ],
  },
  {
    tipsList: [
      {
        position: { x: -199, y: -24, z: 145 },
        content: {
          title: '进入大厅',
          text: '',
          image: 0,
          showTip: false,
          showTitle: true,
        },
      }
    ],
  }
];
const imageList = [leftImage, rightImage, topImage, bottomImage, frontImage, backImage];
let tipsSpriteList:TypeObject[] = [];

export default defineComponent({
  name: 'ThreeJsIndex',
  components: {
    InfoFilled,
  },
  setup() {
    const threeDBox = ref();
    const tooltipBox = ref();
    const titleBox = ref();
    const timer = ref();
    const radioValue = ref('sphere');

    const tooltipPosition = ref<TypeObject>({
      top: '-100%',
      left: '-100%',
    });
    const titlePosition = ref<TypeObject>({
      top: '-100%',
      left: '-100%',
    });
    const tooltopContent = ref<TypeObject>({});

    /**
       * @description: 初始化场景
       */
    const initScene = () => {
      scene = new Three.Scene();
      scene.background = new Three.Color(0xffffff);
    };

    /**
       * @description: 初始化相机
       * @param {*} element
       */
    const initCamera = (element:HTMLElement) => {
      camera = new Three.PerspectiveCamera(
        45, // 视角
        element.offsetWidth / element.offsetHeight,
        0.1, // 最近能看到的距离
        1000 // 最远能看到的距离
      );

      // camera.position.z = 0.2; // 设置相机位置
      camera.position.set(50, 0, 40);
    };

    /**
       * @description: 设置渲染器
       */
    const initRenderer = (element:HTMLElement) => {
      renderer = new Three.WebGLRenderer();
      renderer.setSize(element.offsetWidth, element.offsetHeight);
      element.appendChild(renderer.domElement);
    };

    /**
       * @description: 添加立方体
       */
    const createCube = () => {
      const geometry = new Three.BoxGeometry(100, 100, 100); // 立方体的几何体
      const boxMaterial:TypeObject[] = [];
      imageList.forEach((image:File) => {
        const texture = new Three.TextureLoader().load(image); // 创建纹理
        if (image === topImage || image === bottomImage) {
          texture.rotation = Math.PI;
          texture.center = new Three.Vector2(0.5, 0.5);
        }

        boxMaterial.push(new Three.MeshBasicMaterial({ map: texture })); // 材质
      });
      const cube = new Three.Mesh(geometry, boxMaterial); // 声明立方体物体
      cube.geometry.scale(1, 1, -1);
      scene.add(cube); // 添加到场景中
    };

    /**
       * @description: 添加提示信息
       */
    const addTipsSprite = (index = 0) => {
      const tipTexture = new Three.TextureLoader().load(tipImage);
      const material = new Three.SpriteMaterial({ map: tipTexture });
      tipsSpriteList = [];
      dataList[index].tipsList.forEach((item:TypeObject) => {
        const sprite = new Three.Sprite(material);
        sprite.scale.set(10, 10, 10);
        sprite.position.set(item.position.x, item.position.y, item.position.z);
        sprite.content = item.content;
        tipsSpriteList.push(sprite);
        scene.add(sprite);
      });
    };

    /**
       * @description: 创建球体
       */
    const createSphere = () => {
      const sphereGeometry = new Three.SphereGeometry(16, 50, 50); // 创建球体
      sphereGeometry.scale(16, 16, -16);
      const texture = new Three.TextureLoader().load(livingRoomImage);
      const sphereMaterial = new Three.MeshBasicMaterial({ map: texture });
      sphere = new Three.Mesh(sphereGeometry, sphereMaterial);
      scene.add(sphere);
      addTipsSprite();
    };

    /**
       * @description: 添加控制器
       */
    const initControls = (element:HTMLElement) => {
      controls = new OrbitControls(camera, element);
      controls.minDistance = 1;
      controls.maxDistance = 100;
      controls.enablePan = true; // 设置有一点点阻力的感觉
    };

    /**
       * @description: 发起渲染函数
       * @return {*}
       */
    const render = () => {
      controls.update();
      renderer.render(scene, camera);
      renderer.sortObjects = false;
      timer.value = requestAnimationFrame(render);
      timer.value += 0.015;
    };

    const onResize = () => {
      const element = threeDBox.value;
      if (element) {
        camera.aspect = element.offsetWidth / element.offsetHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(element.offsetWidth, element.offsetHeight);
      }
    };

    const changeContentAndtips = (index:number) => {
      scene.children = scene.children.filter((item: TypeObject) => String(item.type) !== 'Sprite');
      tipsSpriteList = [];
      const texture = new Three.TextureLoader().load(index === 0 ? livingRoomImage : kitchenImage);
      const sphereMaterial = new Three.MeshBasicMaterial({
        map: texture,
        transparent: true,
        opacity: 0,
      });
      sphere.material = sphereMaterial;
      gsap.to(sphereMaterial, { transparent: true, opacity: 1, duration: 2 });
      camera.updateProjectionMatrix();
      addTipsSprite(index);
    };

    const handleTooltipHide = (e:Event) => {
      e.preventDefault();
      tooltipPosition.value = {
        top: '-100%',
        left: '-100%',
      };
      titlePosition.value = {
        top: '-100%',
        left: '-100%',
      };
      tooltopContent.value = {};
    };

    const onMouseClick = (e:any) => {
      e.preventDefault();
      const element = threeDBox.value;
      if (!element) {
        return;
      }

      const raycaster = new Three.Raycaster();
      const mouse = new Three.Vector2();
      mouse.x = (e.offsetX / element.offsetWidth) * 2 - 1;
      mouse.y = -(e.offsetY / element.offsetHeight) * 2 + 1;

      raycaster.setFromCamera(mouse, camera);
      const intersects = raycaster.intersectObjects(tipsSpriteList, true);
      if (intersects.length > 0 && intersects[0].object.content.showTitle) {
        changeContentAndtips(intersects[0].object.content.image);
        handleTooltipHide(e);
      }
    };

    const onMousemove = (e:any) => {
      e.preventDefault();
      const element = threeDBox.value;
      if (!element) {
        return;
      }
      const raycaster = new Three.Raycaster();
      const mouse = new Three.Vector2();
      mouse.x = (e.offsetX / element.offsetWidth) * 2 - 1;
      mouse.y = -(e.offsetY / element.offsetHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      const intersects = raycaster.intersectObjects(tipsSpriteList, true);
      if (intersects.length > 0) {
        const elementWidth = element.offsetWidth / 2;
        const elementHeight = element.offsetHeight / 2;

        const worldVector = new Three.Vector3(
          intersects[0].object.position.x,
          intersects[0].object.position.y,
          intersects[0].object.position.z
        );
        const position = worldVector.project(camera);
        tooltopContent.value = intersects[0].object.content; // 设置弹出信息内容

        nextTick(() => {
          if (intersects[0].object.content.showTip) {
            const left = Math.round(
              elementWidth * position.x
                  + elementWidth
                  - tooltipBox.value.offsetWidth / 2
            );
            const top = Math.round(
              -elementHeight * position.y
                  + elementHeight
                  - tooltipBox.value.offsetHeight - 30
            );
            tooltipPosition.value = {
              left: `${left}px`,
              top: `${top}px`,
            };
          } else if (intersects[0].object.content.showTitle) {
            const left = Math.round(
              elementWidth * position.x
                  + elementWidth
                  + titleBox.value.offsetWidth / 2
            );
            const top = Math.round(-elementHeight * position.y + elementHeight - titleBox.value.offsetHeight / 2);
            titlePosition.value = {
              left: `${left}px`,
              top: `${top}px`,
            };
          }
        });
      } else {
        handleTooltipHide(e);
      }
    };

    const threeDMounted = (type = 'sphere') => {
      const element = threeDBox.value;
      initScene();
      initCamera(element);
      initRenderer(element);
      if (type === 'sphere') {
        createSphere(); // 通过添加球体实现全景查看

        window.addEventListener('click', onMouseClick, false);
        renderer.domElement.addEventListener(
          'mousemove',
          onMousemove,
          false
        );
        tooltipBox.value.addEventListener(
          'mouseleave',
          handleTooltipHide,
          false
        );
      } else {
        createCube(); // 通过添加立方体实现全景查看
      }
      initControls(element);
      render();
      window.addEventListener('resize', onResize, false);
    };

    const radioValueChange = (value:string) => {
      radioValue.value = value;
      // 清除原来画布中的内容;
      const canvas = document.querySelectorAll('canvas');
      if (canvas[0]) {
        canvas[0].remove();
      }
      threeDBox.value.innerHtml = null;
      threeDMounted(radioValue.value);
    };

    onMounted(() => {
      threeDMounted();
    });

    return {
      threeDBox,
      tooltipBox,
      titleBox,
      radioValue,
      radioValueChange,
      tooltipPosition,
      titlePosition,
      tooltopContent,
    };
  },
});
</script>

<style lang="scss" scoped>
.mt-12 {
  margin-top: 12px;
}

.page-container {
  position: relative;
  height: 100%;
  padding: 0;

  .options-wrap {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 999;
    display: flex;
    align-items: center;
    justify-content: space-between;

    .the-radio-button {
      box-sizing: border-box;
      padding: 0 15px;
      font-size: 12px;
      line-height: 26px;
      cursor: pointer;
      background-color: #fff;
      border: 1px solid #ececec;
      border-radius: 2px;

      &.active {
        color: #fff;
        background-color: var(--maia-menu-active-text-color);
        border-color: var(--maia-menu-active-text-color);
      }
    }

    .hover {
      margin-left: 10px;
      font-size: 18px;
      cursor: pointer;
    }
  }

  .three-d-box {
    height: 100%;
  }

  .title-text {
    position: absolute;
    color: #666;
    text-align: center;
  }

  .tooltip-box {
    position: absolute;
    z-index: 100;
    color: #fff;
    cursor: pointer;

    .container {
      position: relative;
      max-width: 240px;
      max-height: 200px;
      background-color: rgb(0 0 0 / 60%);
      border-radius: 4px;

      &::before {
        position: absolute;
        bottom: -8px;
        left: 50%;
        margin-left: -4px;
        content: '';
        border-top: 8px solid rgb(0 0 0 / 60%);
        border-right: 8px solid transparent;
        border-left: 8px solid transparent;
      }

      .title {
        box-sizing: border-box;
        width: 100%;
        padding: 10px 10px 0;
        line-height: 20px;
      }

      .explain {
        box-sizing: border-box;
        width: 100%;
        max-height: 100px;
        padding: 0 10px 10px;
        margin-top: 6px;
        overflow-y: auto;
        font-size: 14px;
        line-height: 20px;

        &::-webkit-scrollbar {
          width: 4px;
          height: 4px;
        }

        &::-webkit-scrollbar-track {
          background: #353535;
          border-radius: 2px;
        }

        &::-webkit-scrollbar-thumb {
          background: #cdcdcd;
          border-radius: 2px;
        }

        &::-webkit-scrollbar-thumb:hover {
          background: #9c9c9c;
        }

        &::-webkit-scrollbar-corner {
          background: #f6f6f6;
        }
      }
    }
  }
}
</style>
